﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using GameEngine.PositionConverter;
using Daphne.GameShared;

namespace GameEngine.PDNParser
{
    /// <summary>
    /// Formats the game out of game manager to the PDN Raw format classes
    /// </summary>
    public class PDNFormatter
    {
        /// <summary>
        /// Supported checkers types
        /// </summary>
        private Dictionary<int, enumGameType> _dicSupportedCheckers = new Dictionary<int, enumGameType>()
        {
            { 20, enumGameType.International },
            { 21, enumGameType.English },
            { 29, enumGameType.Czech }            
        };

        private IPositionConverter _posConverter = null;

        private FENParser _fenParser = null;

        public PDNFormatter(enumGameType gameType)
        {
            _posConverter = PositionConverterFactory.Create(gameType);
        }

        #region Single Line Formatting - meant to be removed in the future until abonding Daphne Win 7
        
        /// <summary>
        /// Converts the model do PDN Raw Model
        /// </summary>
        public PDNRawGame FormatGame(GameManager _mng)
        {
            PDNRawGame game = new PDNRawGame();
            game.GameType = enumGameType.Czech;
            game.SuicialCheckers = false;

            // write FEN
            int moveIndex = _mng.MoveHistory.GetMoveIndex();
            _mng.MoveHistory.MoveToBeginning();
            enumFigureColor onMove = _mng.GameBoard.OnMove;
            if (_mng.GameBoard.IsStartPosition() == false)
            {
                PDNRawFENSetupString fen = new PDNRawFENSetupString();
                FENParser parser = new FENParser(game.GameType);
                string fenValue = parser.WriteFEN(_mng.GameBoard);
                _mng.PDNHeaders[PDNHeaderKey.FENSetup] = fenValue;
            }
            _mng.MoveHistory.MoveToIndex(moveIndex);

            // convert headers
            foreach (var item in _mng.PDNHeaders)
            {
                PDNRawHeader header = new PDNRawHeader();
                header.Key = item.Key;
                header.Value = item.Value;

                game.Headers.Add(header);
            }


            // convert moves
            List<Move> moves = new List<Move>();
            _mng.MoveHistory.GetMovesDone(moves);
            _mng.MoveHistory.GetMovesToDo(moves);

            // check first empty move
            int moveNumber = 1;
            if (onMove == enumFigureColor.Black)
            {
                PDNRawMove move = new PDNRawMove();
                move.MoveNumber = moveNumber;
                move.EmptyMove = true;
                game.GameBody.Items.Add(move);
            }

            // convert moves
            foreach (var move in moves)
            {
                // fill in basic info
                PDNRawMove rawMove = new PDNRawMove();
                rawMove.EmptyMove = false;
                rawMove.CaptureMove = move.RemovingFigure;
                rawMove.MoveNumber = onMove == enumFigureColor.White ? moveNumber : default(int?);

                // convert positions
                Move lastMove = move;
                PDNRawPosition pos = new PDNRawPosition();
                pos.PositionType = enumPDNPositionType.Chess;
                pos.Position = _posConverter.PositionToString(lastMove.startPos, true);
                rawMove.Positions.Add(pos);

                // iterate through all positions
                while (lastMove != null)
                {
                    pos = new PDNRawPosition();
                    pos.PositionType = enumPDNPositionType.Chess;
                    pos.Position = _posConverter.PositionToString(lastMove.endPos, true);
                    rawMove.Positions.Add(pos);
                    lastMove = lastMove.nextMove;
                }

                // save move strength
                rawMove.MoveStrength = move.MoveStrength;

                // add move to the game
                game.GameBody.Items.Add(rawMove);

                if (onMove == enumFigureColor.Black)
                    moveNumber++;
                onMove = onMove == enumFigureColor.White ? enumFigureColor.Black : enumFigureColor.White;
            }          


            return game;
        }

        #endregion

        #region Complete Formatting

        /// <summary>
        /// The main API method - remove root param when model-binding ready
        /// </summary>
        public PDNRawGame FomatCompletely(GameTree root, GameManager gm, bool chessNotation)
        {
            // init
            _fenParser = new FENParser(gm.GameType);

            // check starting position
            int moveIndex = gm.MoveHistory.GetMoveIndex();
            gm.MoveHistory.MoveToBeginning();
            enumFigureColor onMove = gm.GameBoard.OnMove;
            if (gm.GameBoard.IsStartPosition() == false)
            {
                PDNRawFENSetupString fen = new PDNRawFENSetupString();
                string fenValue = _fenParser.WriteFEN(gm.GameBoard);
                gm.PDNHeaders[PDNHeaderKey.FENSetup] = fenValue;
            }
            gm.MoveHistory.MoveToIndex(moveIndex);

            // create GameType tag, including suicidal checkers
            string gameTypeValue = string.Empty;
            foreach (var pair in _dicSupportedCheckers)
            {
                if (pair.Value == gm.GameType)
                {
                    gameTypeValue = pair.Key.ToString();
                    if (gm.SuicidalCheckers)
                        gameTypeValue += "x";
                }
            }
            if (string.IsNullOrEmpty(gameTypeValue))
                throw new Exception("Unsupported checkers type.");
            gm.PDNHeaders[PDNHeaderKey.GameType] = gameTypeValue;

            return ConvertGame(root, gm, chessNotation);
        }

        /// <summary>
        /// Converts the game with PDN headers
        /// </summary>
        private PDNRawGame ConvertGame(GameTree parent, GameManager gm, bool chessNotation)
        {
            PDNRawGame rawGame = new PDNRawGame();

            // convert PDN headers
            foreach (var item in gm.PDNHeaders)
            {
                PDNRawHeader header = new PDNRawHeader();
                header.Key = item.Key;
                header.Value = item.Value;

                rawGame.Headers.Add(header);
            }

            // write the game
            int moveIndex = 1;
            enumFigureColor onMove = enumFigureColor.White;
            rawGame.GameBody = ConvertGameBody(parent, gm, ref moveIndex, ref onMove, chessNotation);

            return null;
        }

        /// <summary>
        /// Switch between the game body items
        /// </summary>
        private PDNRawGameBodyItem ConvertGameBodyItem(GameTree treeItem, GameManager gm, ref int moveIndex, ref enumFigureColor onMove, bool chessNotation)
        {
            GameItem item = treeItem.GameItem;
            if (item is EmptyMove)
            {
                return ConvertEmptyMove(item as EmptyMove);
            }
            else if (item is Move)
            {
                return ConvertMove(item as Move, moveIndex, onMove);
            }
            else if (item is GameVariation)
            {
                return ConvertGameBody(treeItem, gm, ref moveIndex, ref onMove, chessNotation);
            }
            else if (item is GamePosition)
            {
                return ConvertFENSetup(item as GamePosition, chessNotation, gm.GameBoard.Size);
            }
            else if (item is GameComment)
            {
                return ConvertComment(item as GameComment);
            }
            else if (item is GameNAG)
            {
                return ConvertNAG(item as GameNAG);
            }
            else
                throw new Exception("Uknown Game Tree Item");
        }

        /// <summary>
        /// Convert Variation of the main game
        /// </summary>       
        private PDNRawGameBody ConvertGameBody(GameTree parent, GameManager gm, ref int moveIndex, ref enumFigureColor onMove, bool chessNotation)
        {
            PDNRawGameBody item = new PDNRawGameBody();

            foreach (var child in parent.ChildNodes)
            {
                PDNRawGameBodyItem childItem = ConvertGameBodyItem(child, gm, ref moveIndex, ref onMove, chessNotation);
                if (childItem is PDNRawMove)
                {
                    onMove = onMove == enumFigureColor.Black ? enumFigureColor.White : enumFigureColor.Black;
                    if (onMove == enumFigureColor.White)
                        moveIndex++;
                }
                item.Items.Add(childItem);

            }

            return item;
        }
        
        /// <summary>
        /// Convert empty move
        /// </summary>
        private PDNRawGameBodyItem ConvertEmptyMove(EmptyMove move)
        {
            PDNRawMove item = new PDNRawMove();
            item.EmptyMove = true;
            return item;
        }

        /// <summary>
        /// Convert the move with positions
        /// </summary>
        private PDNRawGameBodyItem ConvertMove(Move move, int moveNumber, enumFigureColor onMove)
        {
            // fill in basic info
            PDNRawMove rawMove = new PDNRawMove();
            rawMove.EmptyMove = false;
            rawMove.CaptureMove = move.RemovingFigure;
            rawMove.MoveNumber = onMove == enumFigureColor.White ? moveNumber : default(int?);

            // convert positions
            Move lastMove = move;
            PDNRawPosition pos = new PDNRawPosition();
            pos.PositionType = enumPDNPositionType.Chess;
            pos.Position = _posConverter.PositionToString(lastMove.startPos, true);
            rawMove.Positions.Add(pos);

            // iterate through all positions
            while (lastMove != null)
            {
                pos = new PDNRawPosition();
                pos.PositionType = enumPDNPositionType.Chess;
                pos.Position = _posConverter.PositionToString(lastMove.endPos, true);
                rawMove.Positions.Add(pos);
                lastMove = lastMove.nextMove;
            }

            // save move strength
            rawMove.MoveStrength = move.MoveStrength;

            // copy from single-line code
            return rawMove;
        }

        /// <summary>
        /// Convert FEN Setup String
        /// </summary>
        private PDNRawGameBodyItem ConvertFENSetup(GamePosition position, bool chessNotation, int boardSize)
        {
            PDNRawFENSetupString item = new PDNRawFENSetupString();
            GameBoard gm = new GameBoard(boardSize);
            gm.UnpackGameBoard(position.PackedPosition);
            item.FENSetup = _fenParser.WriteFEN(gm, chessNotation);
            return item;
        }
        
        /// <summary>
        /// Convert Game Comment
        /// </summary>
        private PDNRawGameBodyItem ConvertComment(GameComment comment)
        {
            PDNRawComment item = new PDNRawComment();
            item.Comment = comment.Comment.Trim();
            return item;
        }

        /// <summary>
        /// Convert NAG Glyph
        /// </summary>
        private PDNRawGameBodyItem ConvertNAG(GameNAG nag)
        {
            PDNRawNAG item = new PDNRawNAG();
            item.NagValue = nag.NAGNumber.ToString();
            return item;
        }

        #endregion
    }
}
